package com.jyh.blog.service.impl;


import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jyh.blog.dao.UserDao;
import com.jyh.blog.dao.UserInfoDao;
import com.jyh.blog.dao.UserRoleDao;
import com.jyh.blog.dto.UserAreaDTO;
import com.jyh.blog.dto.UserBackDTO;
import com.jyh.blog.dto.UserInfoDTO;
import com.jyh.blog.entity.User;
import com.jyh.blog.entity.UserInfo;
import com.jyh.blog.entity.UserRole;
import com.jyh.blog.enums.LoginTypeEnum;
import com.jyh.blog.exception.MyException;
import com.jyh.blog.service.BlogInfoService;
import com.jyh.blog.service.RedisService;
import com.jyh.blog.service.UserService;
import com.jyh.blog.strategy.context.SocialLoginStrategyContext;
import com.jyh.blog.utils.PageUtils;
import com.jyh.blog.utils.UserUtils;
import com.jyh.blog.vo.*;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.jyh.blog.constant.CommonConst.*;
import static com.jyh.blog.constant.RedisPrefixConst.*;
import static com.jyh.blog.enums.LoginTypeEnum.EMAIL;
import static com.jyh.blog.enums.RoleEnum.USER;
import static com.jyh.blog.enums.UserAreaTypeEnum.getUserAreaType;
import static com.jyh.blog.utils.CommonUtils.checkEmail;
import static com.jyh.blog.utils.CommonUtils.getRandomCode;

/**
 * (User)表服务实现类
 *
 * @author jyh
 * @since 2023-01-14 12:53:10
 */
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private UserRoleDao userRoleDao;
    @Autowired
    private BlogInfoService blogInfoService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String from;
    @Autowired
    private SocialLoginStrategyContext socialLoginStrategyContext;

    @Override
    public PageResult<UserBackDTO> userBackList(ConditionVO conditionVO) {
        //记录数
        Integer count = userDao.countUser(conditionVO.getLoginType(), conditionVO.getKeywords());
        if (count == 0) {
            return new PageResult<>();
        }
        List<UserBackDTO> userBackDTOList = userDao.userBackList(PageUtils.getLimitCurrent(), PageUtils.getSize(), conditionVO);
        return new PageResult<>(userBackDTOList, count);
    }

    @Override
    public void sendCode(String userMail) {
        if (Objects.isNull(userMail) && !checkEmail(userMail)) {
            throw new MyException("邮箱错误");
        }
        //发送邮件
        String code = getRandomCode();
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("江一行的博客<" + from + ">");
        message.setSubject("网站登录");
        message.setText("验证码：" + code + "（5分钟内有效，若非本人操作，请忽略。）");
        message.setTo(userMail);
        //发邮件
        mailSender.send(message);
        //存入缓存
        redisService.set(USER_CODE_KEY + userMail, code, CODE_EXPIRE_TIME);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(UserVO user) {
        if (checkUser(user)) {
            throw new MyException("该邮箱已注册");
        }
        //用户信息
        UserInfo userInfo = UserInfo.builder()
                .email(user.getUsername())
                .avatar(blogInfoService.getWebSiteConfig().getUserAvatar())
                .nickname(DEFAULT_NICKNAME + IdWorker.getId())
                .build();
        userInfoDao.insert(userInfo);
        //用户账号
        User registerUser = User.builder()
                .username(user.getUsername())
                .password(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()))
                .userInfoId(userInfo.getId())
                .loginType(EMAIL.getType())
                .build();
        this.save(registerUser);
        //用户角色
        UserRole role = UserRole.builder()
                .userId(userInfo.getId())
                .roleId(USER.getRoleId())
                .build();
        userRoleDao.insert(role);
    }

    @Override
    public void updateUserPassword(UserVO userVO) {
        if (!checkUser(userVO)) {
            throw new MyException("用户不存在");
        }
        userDao.update(new User(), new LambdaUpdateWrapper<User>()
                .set(User::getPassword, BCrypt.hashpw(userVO.getPassword(), BCrypt.gensalt()))
                .eq(User::getUsername, userVO.getUsername()));
    }

    @Override
    public void updateAdminPassword(PasswordVO passwordVO) {
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .select(User::getId, User::getPassword)
                .eq(User::getId, UserUtils.getLoginUser().getId()));
        if (Objects.nonNull(user) && BCrypt.checkpw(passwordVO.getOldPassword(), user.getPassword())) {
            user.setPassword(BCrypt.hashpw(passwordVO.getNewPassword(), BCrypt.gensalt()));
            this.updateById(user);
        } else {
            throw new MyException("旧密码错误");
        }
    }

    @Override
    public List<UserAreaDTO> userAreaList(ConditionVO conditionVO) {
        List<UserAreaDTO> userAreaDTOList = new ArrayList<>();
        switch (Objects.requireNonNull(getUserAreaType(conditionVO.getType()))) {
            case USER:
                Object o = redisService.get(USER_AREA);
                if (Objects.nonNull(o)) {
                    userAreaDTOList = JSON.parseObject(o.toString(), List.class);
                }
                break;
            case VISITOR:
                Map<String, Object> visitorMap = redisService.hGetAll(VISITOR_AREA);
                userAreaDTOList = visitorMap.entrySet().stream().map(item -> UserAreaDTO.builder()
                        .name(item.getKey())
                        .value(Long.valueOf(item.getValue().toString()))
                        .build()
                ).collect(Collectors.toList());
                break;
            default:
                break;
        }
        return userAreaDTOList;
    }

    /**
     * QQ登录
     * @param qqLoginVO
     * @return
     */
    @Override
    public UserInfoDTO QQLogin(QQLoginVO qqLoginVO) {
        return socialLoginStrategyContext.execute(LoginTypeEnum.QQ,JSON.toJSONString(qqLoginVO));
    }

    /**
     * 检查用户
     *
     * @param user
     * @return true：用户存在  false：用户不存在
     */
    private boolean checkUser(UserVO user) {
        if (!user.getCode().equals(redisService.get(USER_CODE_KEY + user.getUsername()))) {
            throw new MyException("验证码错误");
        }

        User selectUser = userDao.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getUsername)
                .eq(User::getUsername, user.getUsername()));
        return Objects.nonNull(selectUser);
    }

    /**
     * 用户地区
     * 每天 00:00 执行
     */
    @Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Shanghai")
    public void userArea() {
        Map<String, Long> userAreaMap = userDao.selectList(new LambdaQueryWrapper<User>().select(User::getId, User::getIpSource))
                .stream().map(item -> {
                    if (Strings.isNotBlank(item.getIpSource())) {
                        return item.getIpSource().substring(0, 2)
                                .replaceAll(PROVINCE, "")
                                .replaceAll(CITY, "");
                    }
                    return UNKNOWN;
                }).collect(Collectors.groupingBy(item -> item, Collectors.counting()));
        //转换格式
        List<UserAreaDTO> userAreaDTOList = userAreaMap.entrySet().stream().map(item -> UserAreaDTO.builder()
                .name(item.getKey())
                .value(item.getValue())
                .build()
        ).collect(Collectors.toList());
        //存redis
        redisService.set(USER_AREA, JSON.toJSONString(userAreaDTOList));
    }
}

